package com.gitee.qdbp.base.shiro.session;

import java.util.Date;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.session.InvalidSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.DelegatingSession;
import org.apache.shiro.session.mgt.SessionContext;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 解决DefaultWebSessionManager过于灵敏的问题<br>
 * 修改后每个请求只会从缓存中读取一次(可能引发的问题就是同时发出的多个请求所获取的Session属性有可能不一致)<br>
 * 原先的默认实现类每个请求会有10多次从缓存读取Session<br>
 * 采用Redis等第三方缓存之后这种情况是无法接受的<br>
 * 多次从缓存读取导致的另一个问题是:
 * SecurityUtils.getSubject().getPrincipal()和session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY)
 * 所获取到的用户认证对象不是同一个引用, 结果修改Principal后, session保存到第三方缓存中的信息无法同步修改
 *
 * @author zhaohuihua
 * @version 180207
 */
public class InsensitiveWebSessionManager extends DefaultWebSessionManager {

    private static final Logger log = LoggerFactory.getLogger(InsensitiveWebSessionManager.class);

    /** 更新最近访问时间的间隔 **/
    private long touchInterval = 60 * 1000;

    @Override
    protected Session createExposedSession(Session session, SessionContext context) {
        if (!WebUtils.isWeb(context)) {
            return super.createExposedSession(session, context);
        }
        ServletRequest request = WebUtils.getRequest(context);
        ServletResponse response = WebUtils.getResponse(context);
        SessionKey sessionKey = new RealSessionKey(session, request, response);
        return new DelegatingSession(this, sessionKey);
    }

    protected Session createExposedSession(Session session, SessionKey key) {
        if (!WebUtils.isWeb(key)) {
            return super.createExposedSession(session, key);
        }

        ServletRequest request = WebUtils.getRequest(key);
        ServletResponse response = WebUtils.getResponse(key);
        SessionKey sessionKey = new RealSessionKey(session, request, response);
        return new DelegatingSession(this, sessionKey);
    }

    public void touch(SessionKey key) throws InvalidSessionException {
        if (key instanceof RealSessionKey) {
            RealSessionKey realKey = ((RealSessionKey) key);
            Date lastAccessTime = realKey.getSession().getLastAccessTime();
            if (lastAccessTime != null) {
                long interval = new Date().getTime() - lastAccessTime.getTime();
                if (interval < touchInterval) {
                    log.trace("LastAccessTimeInterval={}, touchInterval={}, do nothing.", interval, touchInterval);
                    return;
                }
            }
        }

        super.touch(key);
    }

    @Override
    protected Session retrieveSession(SessionKey key) throws UnknownSessionException {
        if (key instanceof RealSessionKey) {
            return ((RealSessionKey) key).getSession();
        } else {
            return super.retrieveSession(key);
        }
    }

    @Override
    public void setGlobalSessionTimeout(long globalSessionTimeout) {
        super.setGlobalSessionTimeout(globalSessionTimeout);
        if (sessionDAO != null && sessionDAO instanceof SessionTimeoutAware) {
            SessionTimeoutAware aware = (SessionTimeoutAware) sessionDAO;
            aware.setSessionTimeout(getGlobalSessionTimeout());
        }
    }

    @Override
    public void setSessionDAO(SessionDAO sessionDAO) {
        super.setSessionDAO(sessionDAO);
        if (sessionDAO instanceof SessionTimeoutAware) {
            SessionTimeoutAware aware = (SessionTimeoutAware) sessionDAO;
            aware.setSessionTimeout(getGlobalSessionTimeout());
        }
    }

    public long getTouchInterval() {
        return touchInterval;
    }

    public void setTouchInterval(long touchInterval) {
        this.touchInterval = touchInterval;
    }

}
